“The great wall is made by blocks”
二进制加法
半加器
我们考虑最基本的二进制加法,两位相加,然后再扩展到高位上。对于一个对a, b
两位相加的加法器,我们称其为半加器,输入是a, b
两位,输出为和sum
以及进位carry
半加器实际上就是不考虑输入进位的加法器
全加器
现在我们考虑进位,引入全加器,即在输入端考虑有进位的情况,全加器的真值表如下:
c | b | a | sum(out) | carry(out) |
---|---|---|---|---|
0 | 0 | 0 | 0 | 0 |
0 | 0 | 1 | 1 | 0 |
0 | 1 | 0 | 1 | 0 |
0 | 1 | 1 | 0 | 1 |
1 | 0 | 0 | 1 | 0 |
1 | 0 | 1 | 0 | 1 |
1 | 1 | 0 | 0 | 1 |
1 | 1 | 1 | 1 | 1 |
实际上全加器就是计算( a + b + carry),可以用两个半加器实现,实现方式如下:
多位加法器
现在我们来考虑如何实现多位加法,一样的道理,对于每一位运算,我们可以使用一个全加器,对于第一位,输入的carry设置为0即可,也不难,这里来直接给出HDL实现
1 | // This file is part of www.nand2tetris.org |
这个加法器的一个问题是进位信号在16个门之间传递,效率比较低,有一种方法叫做carry look ahead
,即超前进位加法器,优点是速度快,缺点是器件会增加很多:
负数
如果我们用最高位表示符号,即可得到负数,$-x$可以使用如下公式进行计算:
例如对于二进制1001 (9),对应的负数为$-(2^{4}-9) = (-7)$,我们称$9$为$-7$的补数,此处我们给出一个重要结论,两个负数相加可以表示为它们的补数相加:
由于我们抛弃了最高位,因此得到的补数和所需的负数结果相同。现在我们需要考虑给定一个数$x$,如何获取他的负数$-x$?这样我们就可以将减法转化为加法:$y-x=y+(-x)$。即我们需要如下芯片:
$\textrm{Input}: x$
$\textrm{Output}: -x$
这里我们运用一个小技巧:为了计算$(2^{n}-x)$,我们可以计算$2^n-x=1+(2^{n}-1)-x$,这样做的好处是$2^{n}-1$全部为1,做减法不需要借位,只需要将被减数按位翻转即可,所以这里我们得到了最终的结论,很重要,记住:一个负数等于其正数按位翻转后+1
算数逻辑单元(ALU)
现在我们来构造CPU中最重要的模块:ALU,其模块图如下所示:
ALU:输入两个操作数和待执行的函数(算数或逻辑运算),给出指定结果,对于本课程的Hack ALU,其结构如下所示:
根据六个输入控制位zx, nx, zy, ny, f, no
,可以选择不同的操作,Hack ALU支持的操作如下:
输出立即数 | 一元逻辑运算 | 二元逻辑运算 | 数学运算 | |
---|---|---|---|---|
0 | x | x & y | -x | |
1 | y | x \ | y | -y |
-1 | !x | x+1 | ||
!y | y+1 | |||
x-1 | ||||
y-1 | ||||
x+y | ||||
x-y | ||||
y-x |
同时,该ALU还有两个输出控制位zr, ng
,表示输出是否为0或者为负数,ALU的HDL代码框架如下:
1 | Chip name: ALU |
控制位的真值表如下:
我们解释一下其中几行比较迷惑的
- 计算1:为了计算1,我们可以对齐按位取反,得到b’1110,而b’1110为$-2$,即$-1+(-1)$,故只需要将$x$与$y$归零并按位取反即可
- 计算$-x$:$-x=\overline x+1$,故$-x-1=\overline x$,两边同时取反,可得$\overline{-x-1}=x$,令$x=-x$,即可得到$\overline{x-1}=-x$。
- 计算$x-y$: